package com.ldn.server.mqtt.controller;

import cn.dev33.satoken.annotation.SaCheckLogin;
import com.ldn.common.basic.dispose.dto.PageDto;
import com.ldn.common.basic.dispose.bo.MqttClientInfo;
import com.ldn.common.basic.utils.system.DataUtils;
import com.ldn.database.dao.entities.Page;
import com.ldn.server.mqtt.config.annotation.ServerMqttServiceController;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.dromara.mica.mqtt.codec.MqttQoS;
import org.dromara.mica.mqtt.core.server.model.ClientInfo;
import org.dromara.mica.mqtt.core.server.model.Subscribe;
import org.dromara.mica.mqtt.spring.server.MqttServerTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

/**
 * Mqtt服务端常用功能
 *
 * @author 李大牛家的牛 (scafel@163.com)
 * @product serverless
 * @className ServiceController
 * @createDate 2025/2/10 - 15:52
 * @description Mqtt服务端常用功能
 **/
@Slf4j
@ServerMqttServiceController("service")
public class ServiceController {
    @Resource
    private MqttServerTemplate mqttServer;

    /**
     * showdoc  踢出连线
     * @catalog MQTT相关接口/服务端接口
     * @title 踢出连线
     * @description 踢出连线接口
     * @method get
     * @url _url/mqtt/service/kick/${client}
     * @param clientId 必选 客户端id
     * @return 无
     * @remark 服务端主动踢出客户端
     */
    @PostMapping("kick/{clientId}")
    @SaCheckLogin
    public void kick(@PathVariable String clientId) {
        mqttServer.close(clientId);
    }

    /**
     * showdoc  获取客户端分页列表
     * @catalog MQTT相关接口/服务端接口
     * @title 获取客户端分页列表
     * @description 获取客户端分页列表接口
     * @method post
     * @url _url/mqtt/service/client/list
     * @param dto 分页参数{@link PageDto}接口参数
     * @return Page 客户端列表{@link Page}参数
     * @remark 获取客户端列表
     */
    @PostMapping("client/page")
    @SaCheckLogin
    public Page<MqttClientInfo> getClientPage(@RequestBody PageDto dto) {
        org.tio.utils.page.Page<ClientInfo> page = mqttServer.getClients(dto.getPage(), dto.getSize());
        Page<MqttClientInfo> vo = new Page<>();
        vo.setPageNumber(dto.getPage());
        vo.setPageSize(dto.getSize());
        vo.setTotalRow(page.getTotalRow());
        vo.setTotalPage(page.getTotalPage());
        List<MqttClientInfo> rows = page.getList().stream().map(this::covertClientInfoFormClientInfo).toList();
        vo.setRecords(rows);
        return vo;
    }


    /**
     * 将mqtt客户端信息转换为自定义的Mqtt客户端信息
     *
     * @param clientInfo mqtt客户端信息
     * @return Mqtt客户端信息
     */
    public MqttClientInfo covertClientInfoFormClientInfo(ClientInfo clientInfo) {
        MqttClientInfo info = new MqttClientInfo();
        info.setClientId(clientInfo.getClientId());
        info.setUsername(clientInfo.getUsername());
        info.setKeepAlive(clientInfo.getKeepAlive());
        info.setConnected(clientInfo.isConnected());
        info.setProtoName(clientInfo.getProtoName());
        info.setProtoVer(clientInfo.getProtoVer());
        info.setProtoFullName(clientInfo.getProtoFullName());
        info.setIpAddress(clientInfo.getIpAddress());
        info.setPort(clientInfo.getPort());
        info.setConnectedAt(clientInfo.getConnectedAt());
        info.setCreatedAt(clientInfo.getCreatedAt());
        info.setDecodeQueueSize(clientInfo.getDecodeQueueSize());
        info.setHandlerQueueSize(clientInfo.getHandlerQueueSize());
        info.setSendQueueSize(clientInfo.getSendQueueSize());
        return info;
    }

    /**
     * showdoc  获取服务端统计信息
     * <p>
     *     connections
     *     private long accepted 共接受过连接数
     *     private long size 当前连接数
     *     private long closed 关闭过的连接数
     *     messages
     *     private long handledPackets 处理的包
     *     private long handledBytes 处理的消息字节数
     *     private long receivedPackets 接收的包
     *     private long receivedBytes 接收的字节数
     *     private long sendPackets 发送的包
     *     private long sendBytes 发送的字节数
     *     private double bytesPerTcpReceive 平均每次TCP包接收的字节数
     *     private double packetsPerTcpReceive 平均每次TCP包接收的业务包
     *     nodes
     *     private long clientNodes 客户端节点
     *     private long connections 连接数
     *     private long users 用户数
     * </p>
     * @catalog MQTT相关接口/服务端接口
     * @title 获取服务端统计信息
     * @description 获取服务端统计信息接口
     * @method get
     * @url _url/mqtt/service/stat
     * @return object {@link org.tio.core.stat.vo.StatVo }
     *  {connections:{accepted,size,closed}, messages:{handledPackets, handledBytes,receivedPackets, receivedBytes, sendPackets, sendBytes, bytesPerTcpReceive, packetsPerTcpReceive}, nodes:{clientNodes,connections,users}}
     * @remark 获取服务端统计信息
     */
    @GetMapping("stat")
    @SaCheckLogin
    public Object getStat() {
        return mqttServer.getStat();
    }

    /**
     * showdoc  给指定主题发送消息
     * <p>不对外网开放接口</p>
     *
     * @catalog MQTT相关接口/服务端接口
     * @title 给指定主题发送消息
     * @description 给指定主题发送消息接口
     * @method POST
     * @url _url/mqtt/client/publish/${topic}
     * @param topic 必选 设备类目主题
     * @param body 必选 设备类目主题
     * @return boolean 发送结果 true/false
     * @remark 不对外网开放接口
     */
    @PostMapping("publish/{topic}")
    @SaCheckLogin
    public boolean publishAll(
            @PathVariable("topic") String topic,
            @RequestBody String body
    ) {
        log.info("publishAll topic:{},body:{}", topic, body);
        return mqttServer.publishAll(topic, body.getBytes(StandardCharsets.UTF_8), MqttQoS.QOS2);
    }

    /**
     * showdoc  给指定主题下指定用户发送消息
     * <p>不对外网开放接口</p>
     *
     * @catalog MQTT相关接口/服务端接口
     * @title 给指定用户指定主题发送消息
     * @description 给指定用户指定主题发送消息接口
     * @method POST
     * @url _url/mqtt/client/publish/${topic}/${clientId}
     * @param topic 必选 设备类目主题
     * @param clientId 必选 客户端id
     * @param body 必选 设备类目主题
     * @return boolean 发送结果 true/false
     * @remark 不对外网开放接口
     */
    @PostMapping("publish/{topic}/{clientId}")
    @SaCheckLogin
    public boolean publish(
            @PathVariable("topic") String topic,
            @PathVariable("clientId") String clientId,
            @RequestBody String body
    ) {
        return mqttServer.publish(clientId, topic, body.getBytes(StandardCharsets.UTF_8), MqttQoS.QOS2);
    }

    /**
     * showdoc  获取指定客户端订阅的主题
     * <p>不对外网开放接口</p>
     *
     * @catalog MQTT相关接口/服务端接口
     * @title 获取指定客户端订阅的主题
     * @description 获取指定客户端订阅的主题接口
     * @method GET
     * @url _url/mqtt/client/topic/{clientId}
     * @param clientId 必选 客户端id
     * @return List<String> 客户端订阅的主题
     * @remark 不对外网开放接口
     */
    @GetMapping("topic/{clientId}")
    @SaCheckLogin
    public List<String> getTopic(@PathVariable("clientId") String clientId) {
        // 获取客户端订阅情况
        List<Subscribe> subscriptions = mqttServer.getSubscriptions(clientId);
        List<String> list = new ArrayList<>();
        // 如果没有订阅信息，返回空集合
        if (subscriptions == null) {
            return list;
        }
        // 遍历订阅信息，将订阅信息转换为字符串，并添加到集合中
        subscriptions.forEach(subscribe -> {
            String str = DataUtils.beanToStr(subscribe);
            list.add(str);
        });
        return list;
    }

    /**
     * showdoc  获取所有订阅的主题
     * <p>不对外网开放接口</p>
     *
     * @catalog MQTT相关接口/服务端接口
     * @title 获取所有客户端订阅的主题
     * @description 获取所有客户端订阅的主题接口
     * @method GET
     * @url _url/mqtt/client/all/topic
     * @return List<String> 客户端订阅的主题
     * @remark 不对外网开放接口
     */
    @GetMapping("all/topic")
    @SaCheckLogin
    public List<String> getAllTopic() {
        // 获取客户端订阅情况
        List<ClientInfo> clients = mqttServer.getClients();
        List<String> topicSet = new ArrayList<>();
        // 如果没有订阅信息，返回空集合
        if (clients == null) {
            return new ArrayList<>();
        }
        // 遍历订阅信息，将订阅信息转换为字符串，并添加到集合中
        clients.forEach(clientInfo -> {
            // 获取客户端订阅情况
            List<Subscribe> subscriptions = mqttServer.getSubscriptions(clientInfo.getClientId());
            // 如果没有订阅信息不做处理
            if (!subscriptions.isEmpty()) {
                // 遍历订阅信息，将订阅信息转换为字符串，并添加到集合中
                subscriptions.forEach(subscribe -> {
                    String str = DataUtils.beanToStr(subscribe);
                    topicSet.add(str);
                });
            }
        });
        return topicSet;
    }

}
